﻿using System;
using System.Collections.Generic;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarAnalysis
{
    [Serializable]
    internal sealed class SyntacticExpressionReducer : ISyntacticExpressionReducer
    {
        private readonly GrammarDeclaration _grammar;
        private readonly GrammarTerminalTable _tokens;
        private readonly GrammarNonterminalTable _nonterminals;
        private readonly GrammarProductionTable _productions;

        public SyntacticExpressionReducer(GrammarNonterminalTable nonterminals)
        {
            if (nonterminals == null)
                throw new ArgumentNullException(nameof(nonterminals));
            _grammar = nonterminals.Grammar;
            _tokens = nonterminals.Tokens;
            _nonterminals = nonterminals;
            _productions = nonterminals.Productions;
        }

        private GrammarNonterminalSymbol TryGetCachedSymbol(string text)
        {
            if (text == null)
                throw new ArgumentNullException(nameof(text));
            GrammarNonterminalSymbol sym;
            _nonterminals.AutoDefinedNonterminals.TryGetValue(text, out sym);
            return sym;
        }

        private GrammarNonterminalSymbol GetFreshSymbol(string text, string hint)
        {
            if (text == null)
                throw new ArgumentNullException(nameof(text));
            if (hint == null)
                throw new ArgumentNullException(nameof(hint));
            return _nonterminals.DefineUnnamed(text, hint);
        }

        private GrammarNonterminalSymbol GetFreshOptionalSymbol(string text, GrammarSymbol item)
        {
            if (text == null)
                throw new ArgumentNullException(nameof(text));
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            string hint;
            if (item.HasSimpleName)
                hint = "Optional" + item.Name;
            else
                hint = "_Optional";
            GrammarNonterminalSymbol sym = GetFreshSymbol(text, hint);
            if (item.HasSimpleName)
                sym.HasSimpleName = true;
            if (item.HasSemanticValue)
            {
                if (item.Type.IsNonEmptyList)
                    sym.Type = TypeExpression.NewList(item.Type.AsNonEmptyList.ElementType);
                else if (item.Type.IsNullable)
                    sym.Type = item.Type;
                else
                    sym.Type = TypeExpression.NewNullable(item.Type);
            }
            return sym;
        }

        private GrammarNonterminalSymbol GetFreshListSymbol(string text, GrammarSymbol item, bool isPositive)
        {
            if (text == null)
                throw new ArgumentNullException(nameof(text));
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            string hint;
            if (item.HasSimpleName)
                hint = item.Name + "List";
            else
                hint = "_List";
            GrammarNonterminalSymbol sym = GetFreshSymbol(text, hint);
            if (item.HasSimpleName)
                sym.HasSimpleName = true;
            if (item.HasSemanticValue)
            {
                if (isPositive)
                    sym.Type = TypeExpression.NewNonEmptyList(item.Type);
                else
                    sym.Type = TypeExpression.NewList(item.Type);
            }
            return sym;
        }

        public GrammarSymbol ReduceSyntacticExpression(SyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return n.AcceptSyntacticExpressionReducer(this);
        }

        public GrammarSymbol ReduceEmptySyntacticExpression(EmptySyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            string text = n.ToString();
            GrammarNonterminalSymbol sym = TryGetCachedSymbol(text);
            if (sym != null)
                return sym;
            GrammarNonterminalSymbol lhs = GetFreshSymbol(text, "_Empty");
            _productions.Define(lhs, n);
            return lhs;
        }

        public GrammarSymbol ReduceQuoteStringSyntacticExpression(QuoteStringSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return _tokens.LookupLiteral(n.Value.Value, n.Value.Context);
        }

        public GrammarSymbol ReduceNameReferenceSyntacticExpression(NameReferenceSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            GrammarTerminalSymbol token;
            if (_tokens.NamedTokens.TryGetValue(n.Name.Name, out token))
                return token;
            GrammarNonterminalSymbol sym;
            if (_nonterminals.UserNonterminals.TryGetValue(n.Name.Name, out sym))
                return sym;
            string message = string.Format(SR.GrammarSymbolNotDefined, n.Name.Name.AsSingleQuote());
            NameErrorException e = new NameErrorException(message);
            e.AddLocation(n.Name.Context);
            throw e;
        }

        private SyntacticExpression MakeListNewPattern(SyntacticExpression operand, bool isPositive)
        {
            if (operand == null)
                throw new ArgumentNullException(nameof(operand));
            return isPositive ? operand : SyntacticExpression.NewEmpty();
        }

        private SyntacticExpression MakeListAddPattern(SyntacticExpression lhsRef, SyntacticExpression sep, SyntacticExpression operand)
        {
            if (lhsRef == null)
                throw new ArgumentNullException(nameof(lhsRef));
            if (operand == null)
                throw new ArgumentNullException(nameof(operand));
            if (sep == null)
                return SyntacticExpression.NewConcat(lhsRef, operand);
            return SyntacticExpression.NewConcat(SyntacticExpression.NewConcat(lhsRef, sep), operand);
        }

        private SemanticAction MakeListGetEmptyAction(TypeExpression type)
        {
            if (type == null)
                throw new ArgumentNullException(nameof(type));
            SemanticAction action = SemanticAction.NewFast(Expression.NewDefault(TypeExpression.NewList(type)));
            return action;
        }

        private SemanticAction MakeListCreateAction()
        {
            Expression listType = Expression.NewNameReference(Identifier.New("Sequence"));
            Expression createMethod = Expression.NewAccess(listType, Identifier.New("Create"));
            Argument dollarItem = Argument.NewPositional(Expression.NewSlotValueReference(IntegerLiteral.New(1)));
            ArgumentList args = ArgumentList.New(Sequence.Create(dollarItem));
            Expression listCreate = Expression.NewCall(createMethod, args);
            SemanticAction action = SemanticAction.NewFast(listCreate);
            return action;
        }

        private SemanticAction MakeListAppendAction(bool hasSeparator)
        {
            Expression listType = Expression.NewNameReference(Identifier.New("Sequence"));
            Expression appendMethod = Expression.NewAccess(listType, Identifier.New("Append"));
            Argument dollarList = Argument.NewPositional(Expression.NewSlotValueReference(IntegerLiteral.New(1)));
            Argument dollarItem = Argument.NewPositional(Expression.NewSlotValueReference(IntegerLiteral.New(hasSeparator ? 3 : 2)));
            ArgumentList args = ArgumentList.New(Sequence.Create(dollarList, dollarItem));
            Expression listAppend = Expression.NewCall(appendMethod, args);
            SemanticAction action = SemanticAction.NewFast(listAppend);
            return action;
        }

        private GrammarSymbol ReduceListSyntacticExpression(SyntacticExpression n, SyntacticExpression operand, bool isPositive, StringLiteral separator)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (operand == null)
                throw new ArgumentNullException(nameof(operand));
            string text = n.ToString();
            GrammarNonterminalSymbol sym = TryGetCachedSymbol(text);
            if (sym != null)
                return sym;
            SyntacticExpression sep = null;
            GrammarTerminalSymbol token = null;
            if (separator != null)
            {
                sep = SyntacticExpression.NewQuoteString(separator);
                token = _tokens.LookupLiteral(separator.Value, separator.Context);
            }
            GrammarSymbol item = ReduceSyntacticExpression(operand);
            GrammarNonterminalSymbol lhs = GetFreshListSymbol(text, item, isPositive);
            SyntacticExpression lhsRef = SyntacticExpression.NewNameReference(Identifier.New(lhs.Name));
            SyntacticExpression pattern1 = MakeListNewPattern(operand, isPositive);
            SyntacticExpression pattern2 = MakeListAddPattern(lhsRef, sep, operand);
            SemanticAction action1 = null;
            SemanticAction action2 = null;
            if (lhs.HasSemanticValue)
            {
                action1 = isPositive ? MakeListCreateAction() : MakeListGetEmptyAction(item.Type);
                action2 = MakeListAppendAction(separator != null);
            }
            SyntacticRuleDeclarator declarator1 = SyntacticRuleDeclarator.New(pattern1, null, action1);
            SyntacticRuleDeclarator declarator2 = SyntacticRuleDeclarator.New(pattern2, null, action2);
            GrammarProduction prod1 = _productions.Define(lhs, declarator1);
            GrammarProduction prod2 = _productions.Define(lhs, declarator2);
            if (isPositive)
                prod1.AddSymbol(item);
            prod2.AddSymbol(lhs);
            if (separator != null)
                prod2.AddSymbol(token);
            prod2.AddSymbol(item);
            return lhs;
        }

        public GrammarSymbol ReduceKleeneSyntacticExpression(KleeneSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return ReduceListSyntacticExpression(n, n.Operand, false, null);
        }

        public GrammarSymbol ReduceOptionalSyntacticExpression(OptionalSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            string text = n.ToString();
            GrammarNonterminalSymbol sym = TryGetCachedSymbol(text);
            if (sym != null)
                return sym;
            GrammarSymbol item = ReduceSyntacticExpression(n.Operand);
            GrammarNonterminalSymbol lhs = GetFreshOptionalSymbol(text, item);
            SyntacticExpression pattern1 = SyntacticExpression.NewEmpty();
            SyntacticExpression pattern2 = n.Operand;
            SemanticAction action1 = null;
            SemanticAction action2 = null;
            if (lhs.HasSemanticValue)
            {
                if (lhs.Type.IsList)
                    action1 = SemanticAction.NewFast(Expression.NewDefault(lhs.Type));
                else
                    action1 = SemanticAction.NewFast(Expression.NewNull(NullLiteral.New()));
                action2 = SemanticAction.NewFast(Expression.NewSlotValueReference(IntegerLiteral.New(1)));
            }
            SyntacticRuleDeclarator declarator1 = SyntacticRuleDeclarator.New(pattern1, null, action1);
            SyntacticRuleDeclarator declarator2 = SyntacticRuleDeclarator.New(pattern2, null, action2);
            _productions.Define(lhs, declarator1);
            GrammarProduction prod = _productions.Define(lhs, declarator2);
            prod.AddSymbol(item);
            return lhs;
        }

        public GrammarSymbol ReducePositiveSyntacticExpression(PositiveSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            return ReduceListSyntacticExpression(n, n.Operand, true, null);
        }

        public GrammarSymbol ReduceSequenceSyntacticExpression(SequenceSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.IsNullable)
            {
                SyntacticExpression e = SyntacticExpression.NewOptional(n.WithIsNullable(false));
                return ReduceSyntacticExpression(e);
            }
            return ReduceListSyntacticExpression(n, n.Operand, true, n.Separator);
        }

        public GrammarSymbol ReduceConcatSyntacticExpression(ConcatSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.Left.IsEmpty)
                return ReduceSyntacticExpression(n.Right);
            string text = n.ToString();
            GrammarNonterminalSymbol sym = TryGetCachedSymbol(text);
            if (sym != null)
                return sym;
            Sequence<SyntacticExpression> operands = n.ToConcatOperands();
            GrammarNonterminalSymbol lhs = GetFreshSymbol(text, "_Concat");
            GrammarProduction prod = _productions.Define(lhs, n);
            foreach (SyntacticExpression operand in operands)
            {
                GrammarSymbol item = ReduceSyntacticExpression(operand);
                prod.AddSymbol(item);
            }
            return lhs;
        }

        public GrammarSymbol ReduceUnionSyntacticExpression(UnionSyntacticExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.Left.IsEmpty)
                return ReduceSyntacticExpression(n.Right);
            string text = n.ToString();
            GrammarNonterminalSymbol sym = TryGetCachedSymbol(text);
            if (sym != null)
                return sym;
            Sequence<SyntacticExpression> operands = n.ToUnionOperands();
            GrammarNonterminalSymbol lhs = GetFreshSymbol(text, "_Union");
            foreach (SyntacticExpression operand in operands)
            {
                GrammarProduction prod = _productions.Define(lhs, operand);
                GrammarSymbol item = ReduceSyntacticExpression(operand);
                prod.AddSymbol(item);
            }
            return lhs;
        }

        public void DefineProduction(GrammarNonterminalSymbol owner, SyntacticRuleDeclarator declarator)
        {
            if (owner == null)
                throw new ArgumentNullException(nameof(owner));
            if (declarator == null)
                throw new ArgumentNullException(nameof(declarator));
            GrammarProduction prod = _productions.Define(owner, declarator);
            List<SyntacticExpression> contexts = new List<SyntacticExpression>();
            List<GrammarTerminalSymbol> operators = new List<GrammarTerminalSymbol>();
            Sequence<SyntacticExpression> operands = declarator.Pattern.ToConcatOperands();
            foreach (SyntacticExpression operand in operands)
            {
                GrammarSymbol item = ReduceSyntacticExpression(operand);
                prod.AddSymbol(item);
                if (item.IsTerminal && item.AsTerminal.Precedence != null)
                {
                    contexts.Add(operand);
                    operators.Add(item.AsTerminal);
                }
            }
            if (declarator.Precedence != null)
            {
                GrammarTerminalSymbol prec = _tokens.Lookup(declarator.Precedence);
                if (prec.Precedence == null)
                {
                    string message = string.Format(SR.TerminalNotOperator, prec.Name);
                    GrammarErrorException e = new GrammarErrorException(message);
                    e.AddLocation(prec.Context);
                    throw e;
                }
                prod.Precedence = prec.Precedence.Value;
            }
            else if (operators.Count > 1)
            {
                GrammarErrorException e = new GrammarErrorException(SR.MoreThanOneOperatorDetected);
                foreach (SyntacticExpression context in contexts)
                    e.AddLocation(context.Context);
                throw e;
            }
            else if (operators.Count == 1)
            {
                GrammarTerminalSymbol op = operators[0];
                prod.Precedence = op.Precedence;
            }
        }

        public void DefineProductions(SyntacticDeclarator declarator)
        {
            if (declarator == null)
                throw new ArgumentNullException(nameof(declarator));
            GrammarNonterminalSymbol owner = _nonterminals.LookupNamed(declarator.Name);
            foreach (SyntacticRuleDeclarator rule in declarator.Rules)
                DefineProduction(owner, rule);
        }

        public void DefineProductions(SyntacticDeclaration declaration)
        {
            if (declaration == null)
                throw new ArgumentNullException(nameof(declaration));
            foreach (SyntacticDeclarator item in declaration.Declarators)
                DefineProductions(item);
        }
    }
}

namespace ParserGenerator.SyntacticAnalysis
{
    partial class SyntacticExpression
    {
        public static SyntacticExpression FromConcatOperands(Sequence<SyntacticExpression> operands)
        {
            if (operands == null)
                throw new ArgumentNullException(nameof(operands));
            if (operands.Count == 0)
                return NewEmpty();
            SyntacticExpression n = operands[0];
            for (int i = 1; i < operands.Count; i++)
                n = NewConcat(n, operands[i]);
            return n;
        }

        public static SyntacticExpression FromUnionOperands(Sequence<SyntacticExpression> operands)
        {
            if (operands == null)
                throw new ArgumentNullException(nameof(operands));
            if (operands.Count == 0)
                return NewEmpty();
            SyntacticExpression n = operands[0];
            for (int i = 1; i < operands.Count; i++)
                n = NewUnion(n, operands[i]);
            return n;
        }

        public Sequence<SyntacticExpression> ToConcatOperands()
        {
            if (IsEmpty)
                return Collections.Sequence.GetEmpty<SyntacticExpression>();
            if (!IsConcat)
                return Collections.Sequence.Create(this);
            ConcatSyntacticExpression n = AsConcat;
            Sequence<SyntacticExpression> operands = Collections.Sequence.Create(n.Right);
            while (n.Left.IsConcat)
            {
                n = n.Left.AsConcat;
                operands = Collections.Sequence.Prepend(n.Right, operands);
            }
            if (!n.Left.IsEmpty)
                operands = Collections.Sequence.Prepend(n.Left, operands);
            return operands;
        }

        public Sequence<SyntacticExpression> ToUnionOperands()
        {
            if (IsEmpty)
                return Collections.Sequence.GetEmpty<SyntacticExpression>();
            if (!IsUnion)
                return Collections.Sequence.Create(this);
            UnionSyntacticExpression n = AsUnion;
            Sequence<SyntacticExpression> operands = Collections.Sequence.Create(n.Right);
            while (n.Left.IsUnion)
            {
                n = n.Left.AsUnion;
                operands = Collections.Sequence.Prepend(n.Right, operands);
            }
            if (!n.Left.IsEmpty)
                operands = Collections.Sequence.Prepend(n.Left, operands);
            return operands;
        }
    }
}
